<?php
abstract class NoBusinessBase_Model_BaseMapper
{
   protected $_dbTable;
   
   protected function conditionWhere($w)
   {
      if(!is_array($w))
         return $w;

      if(count($w) == 0)
         return null;

      $string = '';
      foreach($w as $f => $v)
      {
         if($string != '')
            $string .= ' AND ';

         if(is_array($v) && count($v) > 1)
            $string .= "$f " . $v[0]  . " '" . addslashes($v[1]) . "'";
         else
            $string .= "$f = '" . addslashes($v) .  "'";
      }
      return $string;
   }
   
   public function setDbTable($dbTable)
   {
      if (is_string($dbTable))
         $dbTable = new $dbTable();

      if (!$dbTable instanceof Zend_Db_Table_Abstract)
         throw new Exception('Invalid table data gateway provided');

      $this->_dbTable = $dbTable;
      return $this;
   }


   public function getDbTable()
   {
      if (null === $this->_dbTable)
      {
         $this->setDbTable('Application_Model_DbTable_' . ucfirst($this->modelName()));
      }

      return $this->_dbTable;
   }
   
   protected function getSelect(NoBusinessBase_Utility_SortAndRangeRequest $pSortAndRange = null, array $ignoreOrders = null, $count = false, $keepOnCountJoin = array())
   {
      $db      = $this->getDbTable();
      $pSelect = $db->select()->setIntegrityCheck(false);
      if($count === false)
         $pSelect -> from($this->modelName());
      else
         $pSelect->from($this->modelName(), array('COUNT('.$this->modelName().'.Id) as _count'));
      $this->addJoins($pSelect, $count, $keepOnCountJoin);
      if($pSortAndRange)
      {
         $pSelect->limit($pSortAndRange->getLimit(), $pSortAndRange->getStart());
         if(!is_array($ignoreOrders) || !in_array($pSortAndRange->getSort(), $ignoreOrders))
            $pSelect->order($pSortAndRange->getSort() . ' ' . $pSortAndRange->getOrder());
      }
      return $pSelect;
   }
   
   protected function addJoins(Zend_Db_Select & $pSelect, $count = false, $keepOnCountJoin = array())
   {
   }

   public function getIndividualFieldValues(NoBusinessBase_Model_Base & $pModel, $feilds, $modelName = null, $id = null)
   {
      if($id === null)
         $id = $pModel->getId();
      if(!$id)
         return;

      if($modelName === null)
         $modelName = $this->modelName();
      
      $pDb     = $this->getDbTable();
      $pSelect = $pDb->select()->setIntegrityCheck(false);
      $pSelect -> from($modelName, $feilds);
      $pSelect -> where('Id = ?', $id);
      
      $pRow = $pDb->fetchRow($pSelect);
      $this->setRow($pDb->fetchRow($pSelect), $pModel);
   }

   protected function localFetchById($id, NoBusinessBase_Model_BaseMapper & $pObj = null)
   {
      // Return true so that fetchById keep running
      return true;
   }
   protected function preDelete(NoBusinessBase_Model_Base $pModel)
   {
      // Stub for children to implement
      return true;
   }
   protected function postDelete(NoBusinessBase_Model_Base $pDeletedModel, $success)
   {
      // Stub for children to implement
   }
   protected function preSave(NoBusinessBase_Model_Base &$pModel)
   {
      $pModel->saveChangeSet();
   }
   protected function postSave(NoBusinessBase_Model_Base &$pModel, $isNew = false)
   {
      if($isNew)
         return;

      $cacheId = $this->getKeyNameById($pModel->getId());
      if($cacheId)
         $this->saveToCache($pModel, $cacheId);
      $pModel->clearChangeSet();
   }

   protected function postSetRow($row, NoBusinessBase_Model_Base & $pModel)
   {
      $pModel->changeSetReady();
   }

   // This function is called before the DB save call.
   // You can implement this function in your base class
   // to massage the data being saved to the data base.
   //    E.g. Json encode an object as a string before save
   protected function postGetDataAsArray(& $data, NoBusinessBase_Model_Base & $pModel)
   {
      // Stub for children to implement
   }
   
   public function save(NoBusinessBase_Model_Base & $pModel, $forceNew = false)
   {
      $this->preSave($pModel);
      $data = $this->getDataAsArray($pModel, true);
      
      $id = $pModel->getId();
      
      $isNew = false;
      if (null == $id || $forceNew === true)
      {
         $data['Id'] = $id;
         $id = $this->getDbTable()->insert($data);
         
         $pModel->setId($id);
         foreach($pModel->notifiers() as $pNotifier)
            $pNotifier->notifyCreate($pModel);

         $isNew = true;
      }
      else
      {
         $this->getDbTable()->update($data, array('Id = ?' => $id));
         foreach($pModel->notifiers() as $pNotifier)
            $pNotifier->notifyModify($pModel);
      }
      
      $this->postSave($pModel, $isNew);
      return $id;
   }

   protected function createModelFromRow(Zend_Db_Table_Row $row = null)
   {
      if($row === null)
         return null;
      
      $className = "Application_Model_" . ucfirst($this->modelName());
      $pModel = new $className;
      $this->setRow($row, $pModel);
      return $pModel;

   }

   public function getCount(Zend_Db_Table_Select $pSelect)
   {
      $rows = $this->getDbTable()->fetchAll($pSelect);
      if(isset($rows[0]) && isset($rows[0]->_count))
         return (int)$rows[0]->_count;
      return false;
   }

   public function makeModel(Zend_Db_Table_Select $pSelect)
   {
      $row = $this->getDbTable()->fetchRow($pSelect);
      return $this->createModelFromRow($row);
   }

   public function makeModels(Zend_Db_Table_Select $pSelect)
   {
      $rows = $this->getDbTable()->fetchAll($pSelect);

      $models = array();
      foreach($rows as $row)
      {
         $pModel = $this->createModelFromRow($row);
         if($pModel)
            $models[] = $pModel;
      }
      return $models;
   }

   protected function setRow($row, NoBusinessBase_Model_Base & $pModel)
   {
      $rowArray = $row->toArray();
      foreach($rowArray as $column => $value)
      {
         if(strlen($column) <= 1 || $column[0] !== '_' || $column[1] !== '_')
            continue;

         $pModel->addExtraField($column, $value);
      }

      $this->postSetRow($row, $pModel);
      foreach($pModel->notifiers() as $pNotifier)
         $pNotifier->notifyAccess($pModel);
   }
   
   public function delete($passedId)
   {
      if(is_object($passedId))
      {
         $pModel = $passedId;
         $passedId = $pModel->getId();
      }
      else
      {
         $pModel = $this->fetchById($passedId);
         if(!$pModel || !$this->preDelete($pModel))
            return 0;
      }

      $success = $this->deleteModel($pModel);
      $this->removeFromCache($this->getKeyNameById($passedId));
      $this->postDelete($pModel, $success);
      return $success;
   }
   
   protected function deleteModel(NoBusinessBase_Model_Base $pModel)
   {
      $where = $this->getDbTable()->getAdapter()->quoteInto('id = ?', $pModel->getId());
      $result = $this->getDbTable()->delete($where);
      foreach($pModel->notifiers() as $pNotifier)
         $pNotifier->notifyDelete($pModel);
      return $result;
   }

   public function find($id, NoBusinessBase_Model_Base $pModel)
   {
      $result = $this->getDbTable()->find($id);
      if (0 == count($result))
         return false;

      $row = $result->current();
      $this->setRow($row, $pModel);
      return true;
   }
   
   public function fetchAll($where = null, $order = null, $joins = null)
   {
      if(is_object($where) && get_class($where) == 'NoBusinessBase_Utility_SortAndRangeRequest')
      {
         // If the first argument is a pagination and sorting object, just use it and forget about the rest of this function
         $pSortAndRange = $where;
         
         return $this->makeModels($this->getSelect($pSortAndRange));
      }
      
      $where = $this->conditionWhere($where);
      $resultSet = $this->getDbTable()->fetchAll($where, $order);
      $entries   = array();
      foreach ($resultSet as $row)
      {
         $className = "Application_Model_" . ucfirst($this->modelName());
         $entry = new $className;
         $this->setRow($row, $entry);
         $entries[] = $entry;
      }
      return $entries;
   }

   public function fetchByField($name, $value, $order = null)
   {
      $resultRow = $this->getDbTable()->fetchRow("$name = '$value'", $order);
      if($resultRow == null)
         return null;

      $className = "Application_Model_" . ucfirst($this->modelName());
      $entry = new $className;
      $this->setRow($resultRow, $entry);
      return $entry;
   }

   public function fetchById($value, $order = null)
   {
      return $this->fetchByField('Id', $value, $order);
   }

   public function fetchAllByField($fieldName, $value, $order = null)
   {
      return $this->fetchAll("$fieldName = '$value'", $order);
   }
   
   public function fetchRow($where, $order = null)
   {
      $where = $this->conditionWhere($where);
      $resultRow = $this->getDbTable()->fetchRow($where, $order);
      if($resultRow == null)
         return null;

      $className = "Application_Model_" . ucfirst($this->modelName());
      $entry = new $className;
      $this->setRow($resultRow, $entry);
      return $entry;
   }

   public function getKeyNameById($id)
   {
      return 'db_' . ucfirst($this->modelName()) . '_' . $id;
   }

   protected function setRows($resultSet)
   {
      $entries    = array();
      foreach ($resultSet as $row)
      {
         $className = "Application_Model_" . ucfirst($this->modelName());
         $entry = new $className;
         $this->setRow($row, $entry);
         $entries[] = $entry;
      }
      return $entries;
   }

   protected function removeFromCache($id)
   {
      if(!Zend_Registry::isRegistered('Memcache'))
         return;
      
      $pMemcache = Zend_Registry::get('Memcache');
      if(!$pMemcache)
         return;
      
      $pMemcache->remove($id);
   }
   
   protected function loadFromCache($id)
   {
      if(!Zend_Registry::isRegistered('Memcache'))
         return null;

      $pMemcache = Zend_Registry::get('Memcache');
      if(!$pMemcache)
         return null;
      
      $pResult = $pMemcache->load($id);
      // found in cache
      if($pResult)
      {
         if(is_array($pResult))
         {
            foreach($pResult as &$pObj)
               $pObj->cameFromCache(true);
         }
         else
         {
            $pResult->cameFromCache(true);
         }

         return $pResult;
      }

      // not found in cache, look it up in the database
      return null;
   }
   
   protected function saveToCache($pObj, $id)
   {
      if(!Zend_Registry::isRegistered('Memcache'))
         return false;
      $pMemcache = Zend_Registry::get('Memcache');
      if(!$pMemcache)
         return false;

      // Save the result in the cahe so future lookup events will find it
      $pMemcache->save($pObj, $id);
      return true;
   }
}

